﻿namespace Refunctor  

open System
open System.Text
open Reflector
open Reflector.CodeModel
open Reflector.CodeModel.Memory
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Data
open System.Windows.Media
open System.Windows.Media.Imaging
open Microsoft.Win32
open System.IO
open ICSharpCode.AvalonEdit  
open System.Reflection
open System.Collections.Generic

type RefunctorMode = 
        | Interactive = 0
        | ProgramEditor = 1

type TabChangedDelegate = delegate of RefunctorMode -> unit
                                                          
[<Sealed>] 
type internal RefunctorUI(serviceProvider : IServiceProvider) as self =
    inherit System.Windows.Forms.UserControl()
    
    let mainPanel = new System.Windows.Forms.Panel(Dock = System.Windows.Forms.DockStyle.Fill)
    let elementHost = new System.Windows.Forms.Integration.ElementHost(Dock = System.Windows.Forms.DockStyle.Fill)
    let interactiveConsole = new InteractiveConsole()
    let programEditor = new ProgramEditor(serviceProvider) 
    let tabControl = new TabControl() 
    let interactiveTabItem = new TabItem(Header="F# Interactive")        
    let programTabItem = new TabItem(Header="F# Program") 
    let tabChangedEvent = new DelegateEvent<TabChangedDelegate>()   
    
    do
        interactiveTabItem.Content <- interactiveConsole
        programTabItem.Content <- programEditor        
        tabControl.Items.Add(interactiveTabItem) |> ignore    
        tabControl.Items.Add(programTabItem) |> ignore      
        elementHost.Child <- tabControl
        mainPanel.Controls.Add(elementHost)
        self.Controls.Add(mainPanel)

        tabControl.SelectionChanged.Add(fun _ ->
                                            let mode = if (tabControl.SelectedItem :?> TabItem).Header.ToString() = "F# Interactive" then RefunctorMode.Interactive else RefunctorMode.ProgramEditor  
                                            tabChangedEvent.Trigger([|mode|]))

    member self.InteractiveConsole with get() = interactiveConsole   
    member self.ProgramEditor with get() = programEditor 
    member self.TabChanged = tabChangedEvent.Publish

[<Sealed>]
type internal RefunctorUIManager(serviceProvider : IServiceProvider) as self =
    let serviceProvider = serviceProvider
    let commandBarManager = serviceProvider.GetService(typeof<ICommandBarManager>) :?> ICommandBarManager
    let windowManager = serviceProvider.GetService(typeof<IWindowManager>) :?> IWindowManager
    let asmBrowser = serviceProvider.GetService(typeof<IAssemblyBrowser>) :?> IAssemblyBrowser
    let assemblyPath  = ref String.Empty
    let nmspace = ref String.Empty
   
    [<DefaultValue>]
    val mutable assemblyItem : IAssembly
        
    [<DefaultValue>] 
    val mutable private mainUI : RefunctorUI

    [<DefaultValue>] 
    val mutable private menuSeperator : ICommandBarSeparator

    [<DefaultValue>] 
    val mutable private menuButtonRefunctor : ICommandBarButton

    [<DefaultValue>] 
    val mutable private toolbarSeparator : ICommandBarSeparator

    [<DefaultValue>] 
    val mutable private toolbarButtonRefunctor : ICommandBarButton

    [<DefaultValue>] 
    val mutable private contextMenuSeparator : ICommandBarSeparator

    [<DefaultValue>] 
    val mutable private buttonAddReference : ICommandBarButton

    [<DefaultValue>]
    val mutable private buttonAllNamespace : ICommandBarButton

    [<DefaultValue>]
    val mutable private buttonLoadAsmAndOpenAllNamespace : ICommandBarButton

    [<DefaultValue>] 
    val mutable private buttonOpenNamespace : ICommandBarButton

    [<DefaultValue>] 
    val mutable private openNamespaceSeparator : ICommandBarSeparator

    let mutable refunctorMode = RefunctorMode.Interactive

    [<Literal>] 
    let REFLECTOR_TOOL_ID = "Tools"
    [<Literal>] 
    let REFLECTOR_TOOLBAR_ID = "ToolBar"
    [<Literal>] 
    let REFLECTOR_BROWSER_ASSEMBLY_ID = "Browser.Assembly"
    [<Literal>]
    let REFLECTOR_BROWSER_NAMESPACE_ID = "Browser.Namespace"

    do
        asmBrowser.ActiveItemChanged.AddHandler 
            (fun s e ->                     
                match s with
                | :? IAssemblyBrowser as b -> 
                    match b.ActiveItem with
                    | :? IAssembly as asmItem ->
                        assemblyPath := Environment.ExpandEnvironmentVariables(asmItem.Location)
                        self.assemblyItem <- asmItem
                    | :? INamespace as nsItem ->
                        nmspace := nsItem.Name
                    | _ -> ()
                | _ -> ()
            )        

    member self.CreateInteractiveConsole() =
        self.mainUI <- new RefunctorUI(serviceProvider)
        windowManager.Windows.Add(REFUNCTOR_WINDOW_ID, self.mainUI, "Refunctor") |> ignore
        self.mainUI.TabChanged.AddHandler((fun (x:RefunctorMode) -> 
                                            refunctorMode <- x
                                            match x with
                                            | RefunctorMode.Interactive ->                                                
                                                self.buttonLoadAsmAndOpenAllNamespace.Visible <- true
                                                self.buttonOpenNamespace.Visible <- true
                                                self.buttonAllNamespace.Visible <- true
                                            | _ -> 
                                                self.buttonLoadAsmAndOpenAllNamespace.Visible <- false
                                                self.buttonOpenNamespace.Visible <- false
                                                self.buttonAllNamespace.Visible <- false
                                            ))

    member self.AddToolbarItems() =
        self.menuSeperator <- commandBarManager.CommandBars.Item(REFLECTOR_TOOL_ID).Items.AddSeparator()
        use s = self.GetType().Assembly.GetManifestResourceStream("Function.png")
        let img = System.Drawing.Image.FromStream(s)                    
        let showRefunctor = new EventHandler(fun _ _ ->  windowManager.Windows.Item(REFUNCTOR_WINDOW_ID).Visible <- true)
        self.menuButtonRefunctor <- commandBarManager.CommandBars.Item(REFLECTOR_TOOL_ID).Items.AddButton("Refunctor",img, showRefunctor, System.Windows.Forms.Keys.Control ||| System.Windows.Forms.Keys.Alt ||| System.Windows.Forms.Keys.R)
        let count = commandBarManager.CommandBars.Item(REFLECTOR_TOOLBAR_ID).Items.Count
        self.toolbarSeparator <- commandBarManager.CommandBars.Item(REFLECTOR_TOOLBAR_ID).Items.InsertSeparator(count - 2)
        self.toolbarButtonRefunctor <- commandBarManager.CommandBars.Item(REFLECTOR_TOOLBAR_ID).Items.InsertButton(count-1,"Display Refunctor", img, showRefunctor)

    member self.RemoveToolbarMenuItems() = 
        commandBarManager.CommandBars.Item(REFLECTOR_TOOL_ID).Items.Remove(self.menuButtonRefunctor)
        commandBarManager.CommandBars.Item(REFLECTOR_TOOL_ID).Items.Remove(self.menuSeperator)
        commandBarManager.CommandBars.Item(REFLECTOR_TOOLBAR_ID).Items.Remove(self.toolbarSeparator)
        commandBarManager.CommandBars.Item(REFLECTOR_TOOLBAR_ID).Items.Remove(self.toolbarButtonRefunctor)
    
    member private self.AddReference() = 
        match refunctorMode with
        | RefunctorMode.Interactive ->
            self.mainUI.InteractiveConsole.AcceptCommand("#r @\"" +  !assemblyPath + "\";;") |> ignore 
        | _ ->
            self.mainUI.ProgramEditor.AddAsmRef({Name = Path.GetFileNameWithoutExtension(!assemblyPath) ; Location = !assemblyPath})

    member private self.OpenAllNamespace() =          
        let namespaces = new HashSet<string>()                  
        let modules : seq<IModule> = Seq.cast self.assemblyItem.Modules 
        for m in modules do
            let types : seq<ITypeReference> = Seq.cast m.Types
            let typeArr : ITypeReference[] = Array.ofSeq(types) 
            for t in typeArr do namespaces.Add(t.Namespace) |> ignore                                          
        namespaces 
        |> Seq.iter (fun x -> if not (String.IsNullOrWhiteSpace(x)) then self.mainUI.InteractiveConsole.InjectCommand("open " + x + ";;") |> ignore )                        
    
    member private self.AddRefAndOpenAllNamespace() =
        self.AddReference()
        self.OpenAllNamespace()            
    
    member self.AddContextMenuItems() =
        self.contextMenuSeparator <- commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.AddSeparator()
        self.buttonAddReference <- commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.AddButton("Add Reference", (fun _ _ -> self.AddReference()))
        self.buttonAllNamespace <- commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.AddButton("Open All Namespace", (fun _ _ ->  self.OpenAllNamespace()))
        self.buttonLoadAsmAndOpenAllNamespace <- commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.AddButton("Add Ref And Open All Namespace", (fun _ _ -> self.AddRefAndOpenAllNamespace()))
        self.contextMenuSeparator <- commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_NAMESPACE_ID).Items.AddSeparator()
        self.buttonOpenNamespace <- commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_NAMESPACE_ID).Items.AddButton("Open Namespace", (fun _ _ -> self.mainUI.InteractiveConsole.InjectCommand("open " + !nmspace + ";;") |> ignore));                                      
    
    member self.RemoveContextMenuItems() =
        commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.Remove(self.buttonAddReference);
        commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.Remove(self.buttonAllNamespace);
        commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.Remove(self.buttonLoadAsmAndOpenAllNamespace);
        commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_ASSEMBLY_ID).Items.Remove(self.contextMenuSeparator);
        commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_NAMESPACE_ID).Items.Remove(self.openNamespaceSeparator);
        commandBarManager.CommandBars.Item(REFLECTOR_BROWSER_NAMESPACE_ID).Items.Remove(self.buttonOpenNamespace);
            
[<Sealed>]
type RefunctorPackage() =
    [<DefaultValue>]
    val mutable private mainUIManager : RefunctorUIManager
    interface IPackage with
        override self.Load(serviceProvider : IServiceProvider) = 
            self.mainUIManager <- new RefunctorUIManager(serviceProvider)
            self.mainUIManager.CreateInteractiveConsole() 
            self.mainUIManager.AddToolbarItems()
            self.mainUIManager.AddContextMenuItems()
        override self.Unload() = 
            self.mainUIManager.RemoveToolbarMenuItems()
            self.mainUIManager.RemoveContextMenuItems()

[<assembly:AssemblyDescription("Refunctor.dll")>]
[<assembly:AssemblyCompany("F# Community")>]
[<assembly:AssemblyProduct("F# Community's Refunctor")>]
[<assembly: AssemblyVersion("1.0.*")>]
[<assembly: AssemblyFileVersion("1.0.*")>]
do()